function PhotoAlbum() {
    var prefs = new gadgets.Prefs();

    this.url = prefs.getString("url");
    this.visualOption = prefs.getString("visualOption");
    this.maxImages = prefs.getInt("maxImages");
    this.feed = null;
    this.entries = null;
    this.isLoading = true;
    this.isFeedLoaded = false;
    this.isLastPhoto = false;
    this.isStarted = false;
    this.feedLoadFailedTimer = null;
    this.feedLoadFailedCount = 0;
    this.images = [];
    this.captions = [];
    this.imageCount = 0;
    //this.checkForUpdates = false;
    //this.updateInterval = 60000;
}
PhotoAlbum.prototype.initialize = function() {
    var self = this;
    
    $.ajaxSetup({
	cache: true
    });
    
    //Dynamically load external Javascript and CSS files.
    if (this.visualOption == "slideShow") {
	$("<link/>", {
	    rel: "stylesheet",
	    type: "text/css",
	    href: "http://59a316076dac79ec0730-2a58bfb4f47d6e1344f0f6dfc83c4bd5.r69.cf2.rackcdn.com/SlideShow/SlideShow.css"
	 }).appendTo("head");
	
	$.getScript("http://59a316076dac79ec0730-2a58bfb4f47d6e1344f0f6dfc83c4bd5.r69.cf2.rackcdn.com/SlideShow/SlideShow.js", function() {
	    self.loadFeed();		
	});  
    }    
    else if (this.visualOption == "collage") {
	$("<link/>", {
	    rel: "stylesheet",
	    type: "text/css",
	    href: "http://59a316076dac79ec0730-2a58bfb4f47d6e1344f0f6dfc83c4bd5.r69.cf2.rackcdn.com/Collage/Collage.css"
	}).appendTo("head");
	
	$.getScript("http://59a316076dac79ec0730-2a58bfb4f47d6e1344f0f6dfc83c4bd5.r69.cf2.rackcdn.com/Collage/Collage.js", function() {
	    self.loadFeed();		
	});
    }
    else if (this.visualOption == "coverFlow") {
	this.loadFeed();
    }
}
PhotoAlbum.prototype.loadFeed = function() {
    var self = this;

    this.feed = new google.feeds.Feed(this.url);
    this.feed.setResultFormat(google.feeds.Feed.MIXED_FORMAT);
    
    if (this.maxImages >= 0) {
	this.feed.setNumEntries(this.maxImages);
    }
    
    //Start a timer in case there is a problem loading the feed.
    this.feedLoadFailedTimer = setTimeout(function() {
	self.feedLoadFailed();
    }, 5000);
    this.feed.load(function(result) {
	if (!result.error) {
	    clearTimeout(self.feedLoadFailedTimer);
	    self.isFeedLoaded = true;
	    self.entries = result.feed.entries;
	    self.loadPhotos();
	} 
    });
}
PhotoAlbum.prototype.loadPhotos = function() {
    var supportedTypes = new Array("image/bmp", "image/gif", "image/jpeg", "image/png", "image/tiff");
    var self = this,
	prefs = new gadgets.Prefs(),
	rsW = prefs.getInt("rsW"),
	rsH = prefs.getInt("rsH");
    
    //Feed already contains maximum number of images. Clear images and captions and re-populate from feed.
    if ((this.entries.length == this.maxImages) || this.isLoading) {
	this.images = new Array();
	this.captions = new Array();

	for (var i = 0; i < this.entries.length; i++) {
	    var contents = this.entries[i].mediaGroups[0].contents[0];
	    
	    for (var j in supportedTypes) {
		if (contents.type == supportedTypes[j]) {
		    var imageProps = {src: contents.url};
		    
		    this.addCaption(contents);
		    this.images.push(imageProps);
		    this.load(contents.url);
		    
		    break;
		}
	    }
	}
    }
    //Add any new images until the maximum is reached.
    else {
	//To prevent duplicates, check the URLs to see which are new.
	//Use an anonymous function so we can easily return from it once the maxImages limit has been reached.
	(function() {
	    var matchFound = false;

	    for (var i = 0; i < self.entries.length; i++) {
		var contents = self.entries[i].mediaGroups[0].contents[0];
	    
		for (var j in supportedTypes) {
		    if (contents.type == supportedTypes[j]) {
			for (var k = 0; k < self.images.length; k++) {
			    if (contents.url == self.images[k].src) {
				matchFound = true;
				break;	//Move to next image in feed.
			    }
			    else {
				matchFound = false;
			    }
			}
			
			//Image not found. Add it.
			if (!matchFound) {
			    var imageProps = {src: contents.url};
			    //Already at maxImages. Remove last image before adding another.
			    if (self.images.length == self.maxImages) {
				self.captions.pop();
				self.images.pop();
			    }

			    self.addCaption(contents, true);
			    self.images.unshift(imageProps);
			}

			break;
		    }
		}
	    }
	})();
	
	for (var i = 0; i < this.images.length; i++) {
	    this.load(this.images[i].src);
	}
    }
}
PhotoAlbum.prototype.addCaption = function(contents, isFirst) {
    //Store URL with the caption so we can match the caption to the correct image as the images are loaded.
    if (contents.description == null || contents.description == "") {
	if (isFirst) {
	    this.captions.unshift({src: contents.url, caption: contents.title});
	}
	else {
	    this.captions.push({src: contents.url, caption: contents.title});
	}
    }
    else {
	if (isFirst) {
	    this.captions.unshift({src: contents.url, caption: contents.description});
	}
	else {
	    this.captions.push({src: contents.url, caption: contents.description});
	}
    }
}
PhotoAlbum.prototype.load = function(url) {
    if (this.visualOption == "slideShow") {
	this.loadSlideShow(url);
    }
    else if (this.visualOption == "coverFlow") {
	this.loadCoverFlow(url);
    }
    else {
	this.loadCollage(url);
    }
}
PhotoAlbum.prototype.loadSlideShow = function(src) {
    var self = this,
	prefs = new gadgets.Prefs(),
	rsW = prefs.getInt("rsW"),
	rsH = prefs.getInt("rsH"),
	settings = {
	    url: src + "?imgmax=1600&dummyVar=" + Math.ceil(Math.random() * 100),
	    rsW: rsW,
	    rsH: rsH,
	    callback: function (newWidth, newHeight) {
		var i = 0,
		    caption = "";

		$.each(self.captions, function(index, value) {
		    if (value.src == src) {
			caption = value.caption;
			return false;	//Break out of loop.
		    }
		});

		for (i = 0; i < self.images.length; i++) {
		    if (self.images[i].src == src) {
			self.images[i].width = newWidth;
			self.images[i].height = newHeight;
			self.images[i].marginTop = -(newHeight / 2) + "px";
			self.images[i].marginLeft =  -(newWidth / 2) + "px";
			self.images[i].caption = caption;
			break;
		    }
		}
		
		if (!window.slideShow) {
		    window.slideShow = new SlideShow(self.onLastPhotoShown);
		    window.slideShow.initialize();
		}

		if (self.isLoading) {
		    window.slideShow.addImage(self.images[i]);
		}
		
		self.imageCount++;
		self.onImageLoaded();
	    },
	    onerror: function() {
		console.log("Error loading image " + src);
		self.imageCount++;
		self.onImageLoaded();
	    }
	};
	
    scaleToFit(settings);
}
PhotoAlbum.prototype.loadCoverFlow = function(src) {
    var self = this,
	prefs = new gadgets.Prefs(),
	rsW = prefs.getInt("rsW"),
	rsH = prefs.getInt("rsH"),
	settings = {
	    url: src + "?imgmax=1600&dummyVar=" + Math.ceil(Math.random() * 100),
	    rsW: rsW,
	    rsH: rsH,
	    callback: function (newWidth, newHeight) {
		var caption = "";

		$.each(self.captions, function(index, value) {
		    if (value.src == src) {
			caption = value.caption;
			return false;	//Break out of loop.
		    }
		});
		
		for (i = 0; i < self.images.length; i++) {
		    if (self.images[i].src == src) {
			self.images[i].width = newWidth;
			self.images[i].height = newHeight;
			self.images[i].marginTop = -(newHeight / 2) + "px";
			self.images[i].marginLeft =  -(newWidth / 2) + "px";
			self.images[i].caption = caption;
			break;
		    }
		}
		
		self.imageCount++;
		self.onImageLoaded();
	    },
	    onerror: function() {
		console.log("Error loading image " + src);
		self.imageCount++;
		self.onImageLoaded();
	    }
	};
	
    scaleToFit(settings);
}
PhotoAlbum.prototype.loadCollage = function(src) {
    var self = this;
    var prefs = new gadgets.Prefs(),
	maxSize = prefs.getInt("maxSize");
    var settings = {
	url: src + "?imgmax=1600&dummyVar=" + Math.ceil(Math.random() * 100),
	rsW: maxSize,
	rsH: maxSize,
	callback: function (newWidth, newHeight) {
	    var caption = "";
	    
	    $.each(self.captions, function(index, value) {
		if (value.src == src) {
		    caption = value.caption;
		    return false;	//Break out of loop.
		}
	    });
	    
	    for (i = 0; i < self.images.length; i++) {
		if (self.images[i].src == src) {
		    self.images[i].width = newWidth;
		    self.images[i].height = newHeight;
		    self.images[i].caption = caption;
		    break;
		}
	    }
	    
	    if (!window.collage) {
		window.collage = new Collage(self.onLastPhotoShown);
		window.collage.initialize();
	    }
	    
	    if (self.isLoading) {
		window.collage.addImage(self.images[i]);
	    }
	    
	    self.imageCount++;
	    self.onImageLoaded();
	},
	onerror: function() {
	    console.log("Error loading image " + src);
	    self.imageCount++;
	    self.onImageLoaded();
	}
    };
	
    scaleToFit(settings);
}
PhotoAlbum.prototype.onImageLoaded = function() {
    var i = 0,
	numImages = this.images.length;
    
    if (this.visualOption == "slideShow") {
	if (!this.isLoading) {
	    //Show images in same order as feed on all cycles after the first.
	    if (this.imageCount == numImages) {
		for (i = 0; i < numImages; i++) {
		    window.slideShow.addImage(this.images[i]);
		}
		    
		window.slideShow.play();
	    }
	}
	else {
	    //Initialize Gadget after 10 images have been loaded, or after all images have been loaded (if less than 10).
	    if ((this.imageCount == 10) || (this.imageCount == numImages && !this.isStarted)) {
		this.isStarted = true;
		readyEvent();
	    }
	}
	
	if (this.imageCount == numImages) {
	    this.imageCount = 0;
	    this.isStarted = false;
	    this.isLoading = false;
	}
    }
    else if (this.visualOption == "coverFlow") {
	//Not sure images can be easily added to CoverFlow as they are loaded.
	//if ((this.imageCount == 10) || (this.imageCount == this.entries.length && !this.isStarted)) {
	if (this.imageCount == numImages) {
	    if (!window.coverFlow) {
		window.coverFlow = new CoverFlow(this.images, this.onLastPhotoShown);
	    }
	    else {
		window.coverFlow.setPhotos(this.images);
	    }
	    
	    window.coverFlow.render(this.onRendered, this);
	    this.imageCount = 0;
	}
    }
    else {	//Collage
	if (!this.isLoading) {
	    //Show images in same order as feed on all cycles after the first.
	    if (this.imageCount == numImages) {
		for (i = numImages - 1; i >= 0; i--) {
		    window.collage.addImage(this.images[i]);
		}
		    
		window.collage.play();
	    }
	}
	else {
	    //Initialize Gadget after 10 images have been loaded, or after all images have been loaded (if less than 10).
	    if ((this.imageCount == 10) || (this.imageCount == numImages && !this.isStarted)) {
		this.isStarted = true;
		readyEvent();
	    }
	}
	
	if (this.imageCount == numImages) {
	    this.imageCount = 0;
	    this.isStarted = false;
	    this.isLoading = false;
	}
    }
}
PhotoAlbum.prototype.onRendered = function(self) {
    if (self.isLoading) {
	self.isLoading = false;
	readyEvent();
    }
    else {
	if (self.visualOption == "slideShow") {
	    window.slideShow.play();
	}
	else if (self.visualOption == "coverFlow") {
	    window.coverFlow.play();
	    //this.startTimer();
	}
    }
}
//Some problems with this. Implement later.
//PhotoAlbum.prototype.startTimer = function() {
//    var self = this;
//    
//    setTimeout(function() {
//	self.checkForUpdates = true;
//    }, this.updateInterval);
//}
PhotoAlbum.prototype.onLastPhotoShown = function() {
    //Only reload images if at least a minute has passed since the last update.
    //if (this.checkForUpdates) {
	//this.checkForUpdates = false;
	this.isFeedLoaded = false;
	this.isLastPhoto = true;
	doneEvent();
    //}
//    else {
//	this.play();
//    }
}
//This function will be called if the feed fails to load, for example, when there is no Internet connection.
//In that case, continue showing the same photos.
PhotoAlbum.prototype.feedLoadFailed = function() {
    var i,
	numImages = this.images.length;
    
    this.feedLoadFailedCount++;
    
    if (this.visualOption == "slideShow") {
	//Add the images again since they were removed after the last photo was shown.
	for (i = 0; i < numImages; i++) {
	    window.slideShow.addImage(this.images[i]);
	}
	    
	window.slideShow.play();
    }
    else if (this.visualOption == "coverFlow") {
	window.coverFlow.render(this.onRendered, this);
    }
    else if (this.visualOption == "collage") {
	for (i = numImages - 1; i >= 0; i--) {
	    window.collage.addImage(this.images[i]);
	}
	    
	window.collage.play();
    }
}
PhotoAlbum.prototype.play = function() {
    if (this.isFeedLoaded) {
	if (this.visualOption == "slideShow") {
	    window.slideShow.play();
	}
	else if (this.visualOption == "coverFlow") {
	    window.coverFlow.play();
	}
	else {
	    window.collage.play();
	}
    }
    else {
	this.loadFeed();
    }
}
PhotoAlbum.prototype.pause = function() {
    if (this.visualOption == "slideShow") {
	window.slideShow.pause();
    }
    else if (this.visualOption == "coverFlow") {
	window.coverFlow.pause();
    }
    else {
	window.collage.pause();
    }
}